/* Copyright (c) 2011, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.logisim.gui.main;

import java.util.ArrayList;

import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import com.cburch.logisim.circuit.CircuitState;
import com.cburch.logisim.comp.Component;

public class SimulationTreeModel implements TreeModel {
    private ArrayList<TreeModelListener> listeners;
    private SimulationTreeCircuitNode root;
    private CircuitState currentView;

    public SimulationTreeModel(CircuitState rootState) {
        this.listeners = new ArrayList<TreeModelListener>();
        this.root = new SimulationTreeCircuitNode(this, null,
                rootState, null);
        this.currentView = null;
    }

    public CircuitState getRootState() {
        return root.getCircuitState();
    }

    public CircuitState getCurrentView() {
        return currentView;
    }

    public void setCurrentView(CircuitState value) {
        CircuitState oldView = currentView;
        if (oldView != value) {
            currentView = value;

            SimulationTreeCircuitNode node1 = mapToNode(oldView);
            if (node1 != null) {
                fireNodeChanged(node1);
            }


            SimulationTreeCircuitNode node2 = mapToNode(value);
            if (node2 != null) {
                fireNodeChanged(node2);
            }

        }
    }

    private SimulationTreeCircuitNode mapToNode(CircuitState state) {
        TreePath path = mapToPath(state);
        if (path == null) {
            return null;
        } else {
            return (SimulationTreeCircuitNode) path.getLastPathComponent();
        }
    }

    public TreePath mapToPath(CircuitState state) {
        if (state == null) {
            return null;
        }

        ArrayList<CircuitState> path = new ArrayList<CircuitState>();
        CircuitState current = state;
        CircuitState parent = current.getParentState();
        while (parent != null && parent != state) {
            path.add(current);
            current = parent;
            parent = current.getParentState();
        }

        Object[] pathNodes = new Object[path.size() + 1];
        pathNodes[0] = root;
        int pathPos = 1;
        SimulationTreeCircuitNode node = root;
        for (int i = path.size() - 1; i >= 0; i--) {
            current = path.get(i);
            SimulationTreeCircuitNode oldNode = node;
            for (int j = 0, n = node.getChildCount(); j < n; j++) {
                Object child = node.getChildAt(j);
                if (child instanceof SimulationTreeCircuitNode) {
                    SimulationTreeCircuitNode circNode = (SimulationTreeCircuitNode) child;
                    if (circNode.getCircuitState() == current) {
                        node = circNode;
                        break;
                    }
                }
            }
            if (node == oldNode) {
                return null;
            }
            pathNodes[pathPos] = node;
            pathPos++;
        }
        return new TreePath(pathNodes);
    }

    protected SimulationTreeNode mapComponentToNode(Component comp) {
        return null;
    }

    @Override
    public void addTreeModelListener(TreeModelListener l) {
        listeners.add(l);
    }

    @Override
    public void removeTreeModelListener(TreeModelListener l) {
        listeners.remove(l);
    }

    protected void fireNodeChanged(Object node) {
        TreeModelEvent e = new TreeModelEvent(this, findPath(node));
        for (TreeModelListener l : listeners) {
            l.treeNodesChanged(e);
        }
    }

    protected void fireStructureChanged(Object node) {
        TreeModelEvent e = new TreeModelEvent(this, findPath(node));
        for (TreeModelListener l : listeners) {
            l.treeStructureChanged(e);
        }
    }

    private TreePath findPath(Object node) {
        ArrayList<Object> path = new ArrayList<Object>();
        Object current = node;
        while (current instanceof TreeNode) {
            path.add(0, current);
            current = ((TreeNode) current).getParent();
        }
        if (current != null) {
            path.add(0, current);
        }
        return new TreePath(path.toArray());
    }

    @Override
    public Object getRoot() {
        return root;
    }

    @Override
    public int getChildCount(Object parent) {
        if (parent instanceof TreeNode) {
            return ((TreeNode) parent).getChildCount();
        } else {
            return 0;
        }
    }

    @Override
    public Object getChild(Object parent, int index) {
        if (parent instanceof TreeNode) {
            return ((TreeNode) parent).getChildAt(index);
        } else {
            return null;
        }
    }

    @Override
    public int getIndexOfChild(Object parent, Object child) {
        if (parent instanceof TreeNode && child instanceof TreeNode) {
            return ((TreeNode) parent).getIndex((TreeNode) child);
        } else {
            return -1;
        }
    }

    @Override
    public boolean isLeaf(Object node) {
        if (node instanceof TreeNode) {
            return ((TreeNode) node).getChildCount() == 0;
        } else {
            return true;
        }
    }

    @Override
    public void valueForPathChanged(TreePath path, Object newValue) {
        throw new UnsupportedOperationException();
    }
}
